﻿using System;
using System.Net;
using System.Text;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Linq;

namespace Tetriphone
{
    public sealed class netcode
    {
        string _serverIP;
        int _serverPort;
        Socket _connection;
        /*const*/
        string UTF8deliminater = Encoding.UTF8.GetString(new byte[] { 255 }, 0, 1);

        #region Singletone
        netcode()
        {
        }

        public static netcode Instance
        {
            get
            {
                return Nested.instance;
            }
        }

        class Nested
        {
            // Explicit static constructor to tell C# compiler
            // not to mark type as beforefieldinit
            static Nested()
            {
            }

            internal static readonly netcode instance = new netcode();
        }
        #endregion

        public void ConnectToServer(string server, int port)
        {
            _serverIP = server; // gonna need to support dns here too
            _serverPort = port;

            _connection = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            var connetionoperation = new SocketAsyncEventArgs { RemoteEndPoint = new DnsEndPoint(_serverIP, _serverPort) };
            connetionoperation.Completed += new EventHandler<SocketAsyncEventArgs>(connetionoperation_Completed);
            _connection.ConnectAsync(connetionoperation);

        }

        void ReceiveSocket()
        {

            var responseListner = new SocketAsyncEventArgs();
            responseListner.Completed += new EventHandler<SocketAsyncEventArgs>(responseListner_Completed);
            var responseBuffer = new byte[1024];
            responseListner.SetBuffer(responseBuffer, 0, 1024);
            _connection.ReceiveAsync(responseListner);

        }

        string trailingMessage = null;
        void responseListner_Completed(object sender, SocketAsyncEventArgs e)
        {

            var message = Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred);
            if (trailingMessage != null)
            {
                message = trailingMessage + message;
                trailingMessage = null;
            }
            if (string.IsNullOrWhiteSpace(message))
                return; // lost connection?

            var lines = new List<string>(message.Split(UTF8deliminater.ToCharArray()));

            var lastLine = lines.LastOrDefault();
            if (!string.IsNullOrWhiteSpace(lastLine))
            {
                trailingMessage = lastLine;
                lines.RemoveAt(lines.Count - 1);
            }

            foreach (var line in lines)
            {
                if (string.IsNullOrWhiteSpace(line))
                {
                    continue;
                }
                ProcesIncomingMessage(line);
            }





            ReceiveSocket();

        }

        private void ProcesIncomingMessage(string m)
        {
            MessageCallback(m);
            // throw new NotImplementedException();
        }

        public void connetionoperation_Completed(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError != SocketError.Success)
            {
                throw (new Exception("Failed to connect"));

            }

            ReceiveSocket();
            SendLogin("hello");

        }

        void SendSocket(string message)
        {
            // need to terminate the message with \xff... not sure if this is right?
            var buffer = StringToAscii(message + "\xff");

            SocketAsyncEventArgs socketAsyn = new SocketAsyncEventArgs() { RemoteEndPoint = new DnsEndPoint(_serverIP, _serverPort) };
            socketAsyn.SetBuffer(buffer, 0, buffer.Length);
            _connection.SendAsync(socketAsyn);
        }

        public byte[] StringToAscii(string s)
        {
            byte[] retval = new byte[s.Length];
            for (int ix = 0; ix < s.Length; ++ix)
            {
                char ch = s[ix];
                //if (ch <= 0x7f)
                retval[ix] = (byte)ch;
                //else retval[ix] = (byte)'?';
            }
            return retval;
        }



        public void SendLogin(string username)
        {
            string s = "tetrisstart " + username + " 1.13";
            //string[] ipParts = ServerIP.Split('.');
            string[] ipParts = _serverIP.Split('.');  // cant use loopback
            string h = (54 * int.Parse(ipParts[0]) + 41 * int.Parse(ipParts[1]) + 29 * int.Parse(ipParts[2]) + 17 * int.Parse(ipParts[3])).ToString();
            Random r = new Random();
            int dec = r.Next(0, 255);
            dec = 0x42;
            string encodedString = dec.ToString("X2");
            for (int i = 0; i < s.Length; i++)
            {
                dec = ((dec + s[i]) % 255) ^ h[i % h.Length];
                encodedString += dec.ToString("X2");
            }
            SendSocket(encodedString);
        }
        public MCallback MessageCallback { get; set; }
        public delegate void MCallback(string message);



        public void SendField(int index)
        {
            char[] Buffer = new char[500];
            int  i, j, k, d=0;
 
            Buffer = string.Format("f {0} ", index + 1).ToCharArray();
            i = TetrisLogic.strlen(Buffer);
 

 for (j = 0; j < TetrisLogic.FIELDHEIGHT; j++)
  for (k = 0; k < TetrisLogic.FIELDWIDTH; k++) if (TetrisLogic.LastFieldBuffer[k][j] != TetrisLogic.PlayingFields[index][k][j]) d++;
  
 if (d * 3 < TetrisLogic.FIELDHEIGHT * TetrisLogic.FIELDWIDTH)

 {
  for (j = 0; j < TetrisLogic.FIELDHEIGHT; j++)
   for (k = 0; k < TetrisLogic.FIELDWIDTH; k++) if (TetrisLogic.LastFieldBuffer[k][j] != TetrisLogic.PlayingFields[index][k][j])
   {
        Buffer[i] = (char)(TetrisLogic.PlayingFields[index][k][j] + '!');
        i++;
        Buffer[i] = (char)(k + (byte)'3'); i++;
        Buffer[i] = (char)(j + (byte)'3'); i++;
   }
 } else

 {
     for (j = 0; j < TetrisLogic.FIELDHEIGHT; j++)
         for (k = 0; k < TetrisLogic.FIELDWIDTH; k++) Buffer[i] = TetrisLogic.BlockValue[(int)TetrisLogic.PlayingFields[index][k][j]]; i++;
 } 
 Buffer[i] = '\0';

 SendSocket(Buffer);
 Array.Copy(TetrisLogic.LastFieldBuffer, TetrisLogic.PlayingFields[index], TetrisLogic.FIELDWIDTH * TetrisLogic.FIELDHEIGHT);
 //memcpy(&LastFieldBuffer, &PlayingFields[index], sizeof(FIELD));
        }

        public void SendSocket(char[] buffer)
        {
            SendSocket(buffer.ToString());
        }

        private void ProcessMessage(string message)
        {
        }

        public void AddPlayerFormat(char[] Buffer, int i, byte j, byte ClassicAddLine)
        {
            if (ClassicAddLine == 0)
            {
                Buffer.Concat(string.Format("\x01 on \x05"));

                if (i == TetrisLogic.MyPosition)
                    Buffer.Concat(string.Format("\x02"));

                Buffer.Concat(string.Format("\x02"));
                Buffer.Concat(TetrisLogic.PlayerNames[i - 1]);
                
                if (i == TetrisLogic.MyPosition)
                    Buffer.Concat(string.Format("\x02"));
            }

            Buffer.Concat(string.Format("\x01 from \x05"));
            if (j == TetrisLogic.MyPosition)
                Buffer.Concat(string.Format("\x02"));
            Buffer.Concat(TetrisLogic.PlayerNames[i - 1]);
            if (j == TetrisLogic.MyPosition)
                Buffer.Concat(string.Format("\x02"));
        }

    
        public void SendSpecial(char number,char block,char MyPosition)
        {
 	        var temp = string.Format("sb {0} {1} {2}", number, block, MyPosition);
            netcode.Instance.SendSocket(temp);
            netcode.Instance.ProcessMessage(temp);
        }
    
    }
}
